﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using HelixToolkit.Wpf;

namespace GameBoard
{
    /// <summary>
    /// Interaction logic for GameBoardVisual.xaml
    /// </summary>
    public partial class GameBoardVisual : UserControl, IDisposable
    {
        private readonly VisualsViewModel _viewModel = VisualsViewModel.Instance;
        public BoardsViewModel.BoardTypes BoardType { get; private set; }
        private AdjustVisualsViewModel _adjustVisualsViewModel;
        public GameBoardVisual(BoardsViewModel.BoardTypes theType)
        {
            InitializeComponent();
            AddLights();
            _adjustVisualsViewModel = new AdjustVisualsViewModel(Viewport.Camera);
            Loaded += ZoomOnLoad;
            Viewport.Background = new SolidColorBrush(Colors.SkyBlue);
            Viewport.Camera.Position = new Point3D(1, 52, 5);
            Viewport.Camera.UpDirection = new Vector3D(0, 1, 1);
            Viewport.Camera.LookDirection = new Vector3D(0, 0, -1);
            Viewport.PanGesture = new MouseGesture(MouseAction.LeftClick);
            BoardType = theType;
        }

        internal void AddLights()
        {
            Viewport.Lights.Children.Add(new AmbientLight(Colors.White));
            //Viewport.Children.Add(new DefaultLights());
            //Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, 0, 1)));
            //Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, 0, -1)));

            //Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, 1, 0)));
            //Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, -1, 0)));

            //Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(-1, 0, 0)));
            //Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(1, 0, 0)));
        }


        private bool multiSelect;

        internal void Viewport_MouseUp(object sender, MouseButtonEventArgs args)
        {
            if (args.ChangedButton != MouseButton.Left)
            {
                return;
            }
            Point mouseposition = args.GetPosition(Viewport);
            var testpoint3D = new Point3D(mouseposition.X, mouseposition.Y, 0);
            var testdirection = new Vector3D(mouseposition.X, mouseposition.Y, 10);
            var pointparams = new PointHitTestParameters(mouseposition);
            var rayparams = new RayHitTestParameters(testpoint3D, testdirection);

            if ((Keyboard.Modifiers & ModifierKeys.Control) > 0)
            {
                multiSelect = true;
            }
            else
            {
                multiSelect = false;
            }
            //test for a result in the Viewport3D
            VisualTreeHelper.HitTest(Viewport, null, HTResult, pointparams);
        }

       
        internal HitTestResultBehavior HTResult(HitTestResult rawresult)
        {
            //MessageBox.Show(rawresult.ToString());
            var rayResult = rawresult as RayHitTestResult;

            if (rayResult != null)
            {
                var rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;
                if (rayMeshResult != null)
                {
                    return HandleHit(rayMeshResult);
                }
            }
            return HitTestResultBehavior.Stop;
        }

        private HitTestResultBehavior HandleHit(RayMeshGeometry3DHitTestResult rayMeshResult)
        {
            var hitgeo = rayMeshResult.ModelHit as GeometryModel3D;
            if (_viewModel.TapeMeasurerActive)
            {
                Point3D foundPoint = new Point3D(0, 0, 0);
                rayMeshResult.VisualHit.Transform.TryTransform(rayMeshResult.PointHit, out foundPoint);
                _viewModel.HandleTapeMeasurerHit(foundPoint);
                return HitTestResultBehavior.Stop;
            }
            var hitTerrain = rayMeshResult.VisualHit as ITerrainFeature;
            bool isValidHit = false;
            if (hitTerrain != null)
            {
                if (hitTerrain.IsSolid)
                {
                    isValidHit = true;
                }
            }
            var belongsToMoveablePicture = CheckVisualBelongsToMoveablePicture(rayMeshResult.VisualHit);
            if (belongsToMoveablePicture)
            {
                isValidHit = true;
            }
            if (!isValidHit)
            {
                return HitTestResultBehavior.Continue;
            }
            HandleValidHit(rayMeshResult);
            return HitTestResultBehavior.Stop;
        }

        private bool CheckVisualBelongsToMoveablePicture(Visual3D visual3D)
        {
            var ivis = visual3D as IVisual;
            if (ivis != null)
            {
                if (ivis.BelongsToMoveablePicture)
                {
                    return true;
                }
            }
            return false;
        }


        private void HandleValidHit(RayMeshGeometry3DHitTestResult rayMeshResult)
        {
			Point3D foundPoint = new Point3D(0, 0, 0);
			rayMeshResult.VisualHit.Transform.TryTransform(rayMeshResult.PointHit, out foundPoint);
			if (_viewModel.TerrainViewModel.ShapePlacementType == TerrainViewModel.ShapeMode.None)
            {
                bool isSolidTerrain = false;
                var terrainHit = rayMeshResult.VisualHit as ITerrainFeature;
                if (terrainHit != null && terrainHit.IsSolid)
                {
                    isSolidTerrain = true;
                }
                if (!doubleClick && CheckVisualBelongsToMoveablePicture(rayMeshResult.VisualHit))
                {
                    if (!multiSelect)
                        _viewModel.ClearSelectedCharacters();
                    _viewModel.ToggleSelectCharacterFromVisual(rayMeshResult.VisualHit as IVisual);
                }
                //Prevents moving based on character visuals
                else if (doubleClick && isSolidTerrain)
                {
                    if (_viewModel.CharactersToMoveablePicture.Any(x => x.Value.IsSelected))
                    {
                        _viewModel.MoveSelectedPiecesTo(foundPoint);
                    }
                }
                //The hit was valid, but the user didn't move a piece onto solid terrain 
                //or select a piece click a piece. Do nothing.
            }
            else
            {
                DrawShape(foundPoint, false);
            }
        }

        private Cursor _currentMouseCursor = Cursors.Arrow;
        private readonly List<Point3D> _drawnPoints = new List<Point3D>();

        private void DrawShape(Point3D point3D, bool temporaryShape)
        {
            var drawnCount = 0;
            if (!temporaryShape)
            {
                _drawnPoints.Add(point3D);
                drawnCount = _drawnPoints.Count;
            }
            else
            {
                //We're doing a shape preview, don't store the point
                drawnCount = _drawnPoints.Count + 1;
            }
            _viewModel.ClearShapePreview();
            bool shapeCompleted = false;
            switch (_viewModel.TerrainViewModel.ShapePlacementType)
            {
                case TerrainViewModel.ShapeMode.Sphere:
                {
                    _viewModel.DrawSphere(point3D, temporaryShape);
                    shapeCompleted = true;
                    break;
                }
                case TerrainViewModel.ShapeMode.Cone:
                {
                    if (drawnCount == 2)
                    {
                        _viewModel.DrawCone(_drawnPoints.First(), point3D, temporaryShape);
                        shapeCompleted = true;
                    }
                    break;
                }
                case TerrainViewModel.ShapeMode.Line:
                {
                    if (drawnCount == 2)
                    {
                        _viewModel.DrawLine(_drawnPoints.First(), point3D, temporaryShape);
                        shapeCompleted = true;
                    }
                    break;
                }
                case TerrainViewModel.ShapeMode.Square:
                {
                    if (drawnCount == 2)
                    {
                        _viewModel.DrawCube(_drawnPoints.First(), point3D, temporaryShape);
                        shapeCompleted = true;
                    }
                    break;
                }
                case TerrainViewModel.ShapeMode.ObjectFile:
                {
                    if (drawnCount == 1)
                    {
                        _viewModel.DrawObjectFile(point3D, temporaryShape);
                        shapeCompleted = true;
                    }
                    break;
                }
                default:
                    throw new Exception("Unknown shape");
            }
            if (shapeCompleted && !temporaryShape)
            {
                _drawnPoints.Clear();
            }
        }

        internal void SetMouseCursorToAoEFor(TerrainViewModel.ShapeMode shapeMode)
        {
            if (shapeMode == TerrainViewModel.ShapeMode.None)
            {
                _currentMouseCursor = Cursors.Arrow;
            }
            else
            {
                _currentMouseCursor = Cursors.ScrollAll;
            }
            SetMouseCursorToType(_currentMouseCursor);
        }

        private bool doubleClick;

        private void Viewport_DoubleClick(object sender, MouseButtonEventArgs e)
        {
            doubleClick = true;
            Viewport_MouseUp(sender, e);
            doubleClick = false;
        }

        private readonly Dictionary<Guid, ModelVisual3D> _placedVisuals = new Dictionary<Guid, ModelVisual3D>();

        public void RemoveVisual(Guid toRemoveID)
        {
            if (_placedVisuals.ContainsKey(toRemoveID))
            {
                var match = _placedVisuals[toRemoveID];
                if (match is IFlip)
                {
                    _adjustVisualsViewModel.DeregisterFlipVisual(match as IFlip);
                }
                Viewport.Children.Remove(match);
                _placedVisuals.Remove(toRemoveID);
            }
        }

        public void AddVisual(Guid visualGroupID, ModelVisual3D toAdd)
        {
            if (toAdd == null)
                return;
            _placedVisuals.Add(visualGroupID, toAdd);
            var transparentTerrain = toAdd as ITranslucentTerrain;
            if (transparentTerrain != null && transparentTerrain.Opacity!=VisiblityType.Visible)
            {
                Viewport.Children.Add(toAdd);
            }
            else
            {
                Viewport.Children.Insert(0, toAdd);
            }
            if (toAdd is IFlip)
            {
                _adjustVisualsViewModel.RegisterFlipVisual(toAdd as IFlip);
            }
        }

        private readonly Stack<Rect3D> _lastZoom = new Stack<Rect3D>();
        private BoardsViewModel.BoardTypes boardTypes;

        internal void ZoomTo(Rect3D rect3D, int zoomSpeed)
        {
            //When the board isn't loaded, zooming using helix had an odd unfixable crash.
            //this dodges that.
            if (ActualHeight != 0 && ActualWidth != 0)
            {
                Viewport.ZoomExtents(rect3D, zoomSpeed);
            }
            else
            {
                _lastZoom.Push(rect3D);
            }
        }

        private void ZoomOnLoad(object sender, RoutedEventArgs e)
        {
            if (_lastZoom.Count > 0)
            {
                ZoomTo(_lastZoom.Pop(), 0);
                _lastZoom.Clear();
            }
        }

        internal void MatchOtherCamera(ProjectionCamera otherCamera)
        {
            Viewport.Camera.LookDirection = otherCamera.LookDirection;
            Viewport.Camera.Position = otherCamera.Position;
            Viewport.Camera.UpDirection = otherCamera.UpDirection;
        }

        internal ProjectionCamera Camera
        {
            get { return Viewport.Camera; }
        }


        public bool LockRotation
        {
            get { return Viewport.IsRotationEnabled; }
            set { Viewport.IsRotationEnabled = !value; }
        }

        private void Viewport_MouseEnter(object sender, MouseEventArgs e)
        {
            SetMouseCursorToType(_currentMouseCursor);
        }

        private void SetMouseCursorToType(Cursor _currentMouseCursor)
        {
            Mouse.OverrideCursor = _currentMouseCursor;
        }

        private void Viewport_MouseLeave(object sender, MouseEventArgs e)
        {
            Mouse.OverrideCursor = Cursors.Arrow;
        }


        internal void MoveVisual(Guid guid, Point3D dest, TimeSpan duration)
        {
            if (!_placedVisuals.ContainsKey(guid))
            {
                return;
            }
            var matchingVisual = _placedVisuals[guid];
            var moveVis = matchingVisual as IMoveable;
            if (moveVis == null)
                return;
            moveVis.MoveTo(dest, duration);
        }

        internal void ActivateVisuals(Guid guid)
        {
            if (!_placedVisuals.ContainsKey(guid))
            {
                return;
            }
            (_placedVisuals[guid] as IActivatable).Activate();
        }

        internal void DeactivateVisuals(Guid guid)
        {
            if (!_placedVisuals.ContainsKey(guid))
            {
                return;
            }
            (_placedVisuals[guid] as IActivatable).Deactivate();
        }

        internal void SetVisualOpacityTo(List<Guid> targets, VisiblityType opacity)
        {
            foreach (var cur in targets)
            {
                if (_placedVisuals.ContainsKey(cur))
                {
                    var target = _placedVisuals[cur] as ICanBeTranslucent;
                    if (target != null)
                    {
                        target.Opacity = opacity;
                    }
                }
            }
        }

        public void Dispose()
        {
        }

        private DateTime _lastMoved = DateTime.Now;
        private void GameBoardVisual_OnMouseMove(object sender, MouseEventArgs args)
        {
            if (_viewModel.TerrainViewModel.ShapePlacementType == TerrainViewModel.ShapeMode.None)
            {
                return;
            }
            if ((DateTime.Now - _lastMoved).TotalMilliseconds < 15)
            {
                return;
            }
            //Objects take a while to preview. Give the poor thing a break
            
            if (_viewModel.TerrainViewModel.ShapePlacementType == TerrainViewModel.ShapeMode.ObjectFile
                && (DateTime.Now - _lastMoved).TotalMilliseconds < 250)
            {
                return;
            }
            _lastMoved = DateTime.Now;
            Point mouseposition = args.GetPosition(Viewport);
            var pointparams = new PointHitTestParameters(mouseposition);
            //test for a result in the Viewport3D
            VisualTreeHelper.HitTest(Viewport, null, MouseMoveHTResult, pointparams);
        }
        internal HitTestResultBehavior MouseMoveHTResult(HitTestResult rawresult)
        {
            //MessageBox.Show(rawresult.ToString());
            var rayResult = rawresult as RayHitTestResult;

            if (rayResult != null)
            {
                var rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;
                if (rayMeshResult != null)
                {
                    return MouseMoveHit(rayMeshResult);
                }
            }
            return HitTestResultBehavior.Stop;
        }

        private HitTestResultBehavior MouseMoveHit(RayMeshGeometry3DHitTestResult rayMeshResult)
        {
            var hitTerrain = rayMeshResult.VisualHit as ITerrainFeature;
            bool isValidHit = false;
            if (hitTerrain != null)
            {
                if (hitTerrain.IsSolid)
                {
                    isValidHit = true;
                }
            }
            if (!isValidHit)
            {
                return HitTestResultBehavior.Continue;
            }

            Point3D foundPoint = new Point3D(0, 0, 0);
            rayMeshResult.VisualHit.Transform.TryTransform(rayMeshResult.PointHit, out foundPoint);
            DrawShape(foundPoint, true);
            return HitTestResultBehavior.Stop;
        }

        List<KeyToDirection> MovementKeys = new List<KeyToDirection>()
            {
                new KeyToDirection(Key.NumPad1, KeyToDirection.MovementDirection.SW),
                new KeyToDirection(Key.NumPad2, KeyToDirection.MovementDirection.S),
                new KeyToDirection(Key.NumPad3, KeyToDirection.MovementDirection.SE),
                new KeyToDirection(Key.NumPad4, KeyToDirection.MovementDirection.W),
                new KeyToDirection(Key.NumPad6, KeyToDirection.MovementDirection.E),
                new KeyToDirection(Key.NumPad7, KeyToDirection.MovementDirection.NW),
                new KeyToDirection(Key.NumPad8, KeyToDirection.MovementDirection.N),
                new KeyToDirection(Key.NumPad9, KeyToDirection.MovementDirection.NE),
                new KeyToDirection(Key.Up, KeyToDirection.MovementDirection.N),
                new KeyToDirection(Key.Down, KeyToDirection.MovementDirection.S),
                new KeyToDirection(Key.Left, KeyToDirection.MovementDirection.W),
                new KeyToDirection(Key.Right, KeyToDirection.MovementDirection.E)
            };
        private void GameBoardVisual_OnPreviewKeyDown(object sender, KeyEventArgs e)
        {

            var moveDir = (MovementKeys.FirstOrDefault(x => x.TheInput == e.Key));
            if(moveDir!=null)
            {
                _viewModel.MovePiecesInDirection(moveDir.DesiredDirection);
                e.Handled = true;
            }
        }

        
    }
}